package jp.seraph.jsade.sexpression;

import java.text.ParseException;
import java.util.ArrayList;
import java.util.List;
import java.util.regex.Matcher;
import java.util.regex.Pattern;

/**
 * S式の文字列表現ををParseしてSExpressionを返すパーザ<br>
 * 手書き
 *
 */
public class DefaultParser implements ExpressionParser {
    private DefaultParser() {

    }

    private static DefaultParser INSTANCE = new DefaultParser();

    public static DefaultParser getInstance() {
        return INSTANCE;
    }

    
    public List<SExpression> parse(String aExpression) throws ParseException {
        Lexer tLexer = new Lexer(aExpression);
        List<SExpression> tResult = new ArrayList<SExpression>();

        while (tLexer.hasNext()) {
            if (tLexer.nextIsAtom())
                tResult.add(this.parseAtom(tLexer));
            else if (tLexer.nextIsLeftCase())
                tResult.add(this.parseList(tLexer));
            else
                throw new ParseException("S式の先頭には'('かAtomが無ければなりません。 : " + tLexer.toString(), 0);
        }

        return tResult;
    }

    private Node parseList(Lexer aLexer) throws ParseException {
        Node tResult = new DefaultNode();
        aLexer.nextLeftCase();

        while (!aLexer.nextIsRightCase()) {
            if (aLexer.nextIsAtom())
                tResult.addChild(this.parseAtom(aLexer));
            else if (aLexer.nextIsLeftCase())
                tResult.addChild(this.parseList(aLexer));
            else
                throw new ParseException("S式のリストの各要素は、リストかAtomでないといけません。　また、S式のリストは')'で終えなければなりません。 " + aLexer.toString(), -1);
        }

        aLexer.nextRightCase();
        return tResult;
    }

    private Leaf parseAtom(Lexer aLexer) {
        return new DefaultLeaf(aLexer.nextAtom());
    }

    private class Lexer {
        public Lexer(String aExpression) {
            mExpression = aExpression;
        }

        private String mExpression;

        private final Pattern ATOM_PATTERN = Pattern.compile("^[^\\s()]+");
        private final Pattern LC_PATTERN = Pattern.compile("^\\(");
        private final Pattern RC_PATTERN = Pattern.compile("^\\)");
        private final Pattern BLANK_PATTERN = Pattern.compile("^\\s+");

        public String nextToken() throws ParseException {
            if (this.nextIsLeftCase())
                return this.nextLeftCase();
            else if (this.nextIsRightCase())
                return this.nextRightCase();
            else if (this.nextIsAtom())
                return this.nextAtom();
            else if (mExpression.length() == 0)
                return null;
            else
                throw new ParseException("字句解析不可能な文字が含まれています。 " + mExpression, 0);
        }

        private String nextToken(Matcher aMatcher) {
            if (nextIs(aMatcher)) {
                mExpression = aMatcher.replaceFirst("");
                return aMatcher.group();
            } else
                return null;
        }

        private boolean nextIs(Matcher aMatcher) {
            return aMatcher.find();
        }

        public String nextAtom() {
            RemoveNextBlank();
            return nextToken(ATOM_PATTERN.matcher(mExpression));
        }

        public String nextLeftCase() {
            RemoveNextBlank();
            return nextToken(LC_PATTERN.matcher(mExpression));
        }

        public String nextRightCase() {
            RemoveNextBlank();
            return nextToken(RC_PATTERN.matcher(mExpression));
        }

        public boolean nextIsAtom() {
            RemoveNextBlank();
            return nextIs(ATOM_PATTERN.matcher(mExpression));
        }

        public boolean nextIsLeftCase() {
            RemoveNextBlank();
            return nextIs(LC_PATTERN.matcher(mExpression));
        }

        public boolean nextIsRightCase() {
            RemoveNextBlank();
            return nextIs(RC_PATTERN.matcher(mExpression));
        }

        private void RemoveNextBlank() {
            nextToken(BLANK_PATTERN.matcher(mExpression));
        }

        
        public String toString() {
            return mExpression;
        }

        public boolean hasNext() {
            return mExpression.length() > 0;
        }
    }
}
